package com.lps.util.http;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.entity.ContentType;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author liangquanyi
 * @since 2021/11/22 10:31
 */
public class RequestUtils {

    /* ================================ params ================================ */

    /**
     * 解析request的参数、
     * 如果有多个相同的key, 获取最后一个键值对
     *
     * @param request request
     * @return 参数map
     */
    @NonNull
    public static Map<String, String> getRequestParams(@NonNull final ServletRequest request) {
        return request.getParameterMap().entrySet().stream()
                .filter(entry -> StringUtils.isNotBlank(entry.getKey()))
                .filter(entry -> ArrayUtils.isNotEmpty(entry.getValue()))
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        entry -> entry.getValue()[entry.getValue().length - 1]
                ));
    }

    /**
     * 解析request的参数
     * 如果有多个相同的key, 获取最后一个键值对
     *
     * @param request request
     * @return 参数map(参数按请求顺序返回)
     */
    @NonNull
    public static Map<String, String> getRequestLinkedParams(@NonNull final ServletRequest request) {
        return request.getParameterMap().entrySet().stream()
                .filter(entry -> StringUtils.isNotBlank(entry.getKey()))
                .filter(entry -> ArrayUtils.isNotEmpty(entry.getValue()))
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        entry -> entry.getValue()[entry.getValue().length - 1],
                        (v1, v2) -> v2,
                        LinkedHashMap::new
                ));
    }

    /**
     * 解析request的参数
     *
     * @param request request
     * @return 参数map
     */
    @NonNull
    public static Map<String, String[]> getRequestParamList(@NonNull final ServletRequest request) {
        return request.getParameterMap().entrySet().stream()
                .filter(entry -> StringUtils.isNotBlank(entry.getKey()))
                .filter(entry -> ArrayUtils.isNotEmpty(entry.getValue()))
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue
                ));
    }

    /* ================================ body ================================ */

    /**
     * 获取request body传递的参数
     * 未进行http转义, 可以调用
     *
     * @param request request
     * @return request body
     */
    @NonNull
    public static String getRequestBodyString(@NonNull final ServletRequest request) {
        final StringBuilder sb = new StringBuilder();
        try (final InputStream inputStream = request.getInputStream();
             final InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
             final BufferedReader reader = new BufferedReader(inputStreamReader)) {
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return sb.toString();
    }

    /* ================================ body ================================ */

    /**
     * 获得request请求头参数
     *
     * @param request request
     * @return 请求头参数键值对
     */
    public static Map<String, String> getRequestHeaders(@NonNull final HttpServletRequest request) {
        final Enumeration<String> headerNames = request.getHeaderNames();
        Map<String, String> headers = new HashMap<>();
        while (headerNames.hasMoreElements()) {
            String key = headerNames.nextElement();
            String value = request.getHeader(key);
            headers.put(key, value);
        }
        return headers;
    }

    /* ================================ request url ================================ */

    /**
     * 构建完整url
     * 未进行特殊字符的转义
     *
     * @param baseUrl 基础url 不带?的url前缀
     * @param params  参数map
     * @return 完整url
     */
    @NonNull
    public static String buildRequestUrl(@NonNull final String baseUrl,
                                         @NonNull final Map<String, String> params) {
        // 获得参数的url
        final String paramStr = params.entrySet().stream()
                // 过滤掉key为空的参数
                .filter(param -> StringUtils.isNotBlank(param.getKey()))
                // key=value
                .map(param -> urlEncode(param.getKey()) + '=' + urlEncode(param.getValue()))
                // 每个参数对用&连接
                .collect(Collectors.joining("&"));

        // 如果参数为空, 直接返回baseUrl
        if (StringUtils.isBlank(paramStr)) {
            return baseUrl;
        }
        return baseUrl + '?' + paramStr;
    }

    /**
     * URL编码 使用字符集为UTF-8
     *
     * @param value 需要编码的值
     * @return URL编码后的值
     */
    @NonNull
    public static String urlEncode(@Nullable final String value) {
        if (null == value) {
            return "";
        }
        return URLEncoder.encode(value, StandardCharsets.UTF_8);
    }

    /**
     * get请求url, 获取返回参数
     *
     * @param url               访问url
     * @param requestProperties 请求头参数map key不能为null
     * @param connectTimeout    连接超时(单位:milliseconds),如果设置0则不设置超时
     * @throws IOException url获取连接失败
     * @deprecated use {@link HttpClientUtils#getStringFromHttpGet(URI, Map, RequestConfig, boolean)} instead
     */
    @NonNull
    @Deprecated
    public static String readContentFromGet(@NonNull final URL url,
                                              @NonNull final Map<String, String> requestProperties,
                                              final int connectTimeout)
            throws IOException {

        // open connection
        final HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        // set connection properties
        connection.setRequestMethod("GET");
        connection.setRequestProperty(HttpHeaders.CONTENT_TYPE, ContentType.TEXT_HTML.withCharset(StandardCharsets.UTF_8).toString());
        connection.setRequestProperty(HttpHeaders.ACCEPT_CHARSET, StandardCharsets.UTF_8.name());
        connection.setConnectTimeout(connectTimeout);
        requestProperties.forEach(connection::setRequestProperty);

        // connect!
        connection.connect();

        // read the result
        try (final InputStream inputStream = connection.getInputStream();
             final InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
             final BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {

            final StringBuilder res = new StringBuilder();
            String lines;
            while ((lines = bufferedReader.readLine()) != null) {
                res.append(lines);
            }
            return res.toString();
        } catch (IOException e) {
            throw new IllegalArgumentException("url connect failed!", e);
        } finally {
            // close the connection
            connection.disconnect();
        }
    }

    /**
     * post请求url, 获取返回参数
     *
     * @param url               访问url
     * @param requestProperties 请求头参数map key不能为null
     * @param body              请求体
     * @param connectTimeout    连接超时(单位:milliseconds),如果设置0则不设置超时
     * @throws IOException url获取连接失败
     * @deprecated use {@link HttpClientUtils#httpPost(URI, Map, HttpEntity, RequestConfig, boolean, Function)} instead
     */
    @Deprecated
    @NonNull
    public static String readContentFromPost(@NonNull final URL url,
                                             @NonNull final Map<String, String> requestProperties,
                                             @NonNull final String body,
                                             final int connectTimeout)
            throws IOException {

        // open connection
        final HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        // set connection properties
        connection.setDoOutput(true);
        connection.setDoInput(true);
        connection.setRequestMethod("POST");
        connection.setUseCaches(false);// Post 请求不能使用缓存
        connection.setInstanceFollowRedirects(true);// URLConnection.setInstanceFollowRedirects是成员函数，仅作用于当前函数
        connection.setConnectTimeout(connectTimeout);
        requestProperties.forEach(connection::setRequestProperty);

        // connect!
        // 要注意的是connection.getOutputStream会隐式的进行connect。
        connection.connect();

        // request body
        try (OutputStream outputStream = connection.getOutputStream();
             OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) {
            writer.write(body);
            writer.flush();
        } catch (IOException e) {
            throw new IllegalArgumentException("url connect failed!", e);
        }

        // read the result
        try (final InputStream inputStream = connection.getInputStream();
             final InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
             final BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {

            final StringBuilder res = new StringBuilder();
            String lines;
            while ((lines = bufferedReader.readLine()) != null) {
                res.append(lines);
            }
            return res.toString();
        } catch (IOException e) {
            throw new IllegalArgumentException("url connect failed!", e);
        } finally {
            // close the connection
            connection.disconnect();
        }
    }

    private RequestUtils() {
    }
}
